package com.yeskery.nut.aop.handler;

import com.yeskery.nut.annotation.aop.Compose;
import com.yeskery.nut.annotation.aop.Throwing;
import com.yeskery.nut.aop.Execution;
import com.yeskery.nut.bean.ApplicationContext;
import com.yeskery.nut.core.Order;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Throwing注解的方法处理器
 * @author sprout
 * @version 1.0
 * 2022-08-28 13:21
 */
public class ThrowingProxyMethodHandler extends AspectProxyMethodHandler implements ComposeProxyMethodHandler {

    /** 日志对象 */
    private static final Logger logger = Logger.getLogger(ThrowingProxyMethodHandler.class.getName());

    /**
     * 构建Throwing注解的方法处理器
     * @param applicationContext 应用上下文
     */
    public ThrowingProxyMethodHandler(ApplicationContext applicationContext) {
        super(applicationContext);
    }

    @Override
    public boolean support(Method method) {
        return getProxyMethodAspectCacheMap().containsKey(method.toString()) || method.isAnnotationPresent(Throwing.class)
                || support(method, c -> c.throwingArray().length > 0);
    }

    @Override
    public void throwing(Method method, Execution execution, Object target, Throwable throwable) throws Exception {
        List<ProxyMethodTarget> proxyMethodTargetList = new ArrayList<>();
        Throwing throwingAnnotation = method.getAnnotation(Throwing.class);
        if (throwingAnnotation != null) {
            proxyMethodTargetList.add(new ProxyMethodTarget(throwingAnnotation.beanName(), throwingAnnotation.beanType(),
                    throwingAnnotation.method(), throwingAnnotation.cause(), throwingAnnotation.order()));
        }
        Compose compose = getComposeAnnotation(method);
        if (compose != null) {
            for (Throwing throwing : compose.throwingArray()) {
                proxyMethodTargetList.add(new ProxyMethodTarget(throwing.beanName(), throwing.beanType(),
                        throwing.method(), throwing.cause(), throwing.order()));
            }
        }

        appendAspectProxyTarget(method, Throwing.class, proxyMethodTargetList);
        proxyMethodTargetList.sort(Comparator.comparing(Order::getOrder));
        outer: for (ProxyMethodTarget proxyMethodTarget : proxyMethodTargetList) {
            boolean caused = false;
            for (Class<? extends Exception> exceptionClass : proxyMethodTarget.getCauseException()) {
                if (exceptionClass.isAssignableFrom(throwable.getClass())) {
                    caused = true;
                    break;
                }
            }
            if (caused) {
                Object object = getTargetObject(target, proxyMethodTarget.getBeanName(), proxyMethodTarget.getBeanType());
                Method invokeMethod;
                try {
                    invokeMethod = object.getClass().getMethod(proxyMethodTarget.getMethodName());
                    invokeMethod.invoke(object);
                } catch (NoSuchMethodException e) {
                    Class<?> throwableClass = throwable.getClass();
                    while (throwableClass != null) {
                        try {
                            invokeMethod = object.getClass().getMethod(proxyMethodTarget.getMethodName(), throwableClass);
                            invokeMethod.invoke(object, throwable);
                            continue outer;
                        } catch (NoSuchMethodException e1) {
                            throwableClass = throwableClass.getSuperclass();
                        }
                    }
                    logger.logp(Level.SEVERE, ThrowingProxyMethodHandler.class.getName(), "throwing",
                            "Throwing Proxy Object Class[" + object.getClass().getName() + "] Method["
                                    + proxyMethodTarget.getMethodName() + "] Can Not Found.");
                }
            }
        }
    }
}
